home *** CD-ROM | disk | FTP | other *** search
/ The Fatted Calf / The Fatted Calf.iso / Applications / Games / Tetris / Source / TetMatrix.m < prev    next >
Text File  |  1975-04-26  |  13KB  |  570 lines

  1. #import <appkit/NXImage.h>
  2. #import <ctype.h>
  3. #import <dpsclient/wraps.h>
  4. #import <math.h>
  5. #import <appkit/graphics.h>
  6. #import "TetMatrix.h"
  7. #import "Piece.h"
  8. #import "ScoreKeeper.h"
  9. #import "TetApp.h"
  10. #import "wraps.h"
  11.  
  12. // The number of pixels a block moves every step
  13. // Make it a divisor of the block size
  14. static const float ANM_DELTA  = 8.0;
  15.  
  16. const float GAMEOVER_BMAP_H = 100.0;
  17.  
  18. extern long random();
  19. extern void srandom(int seed);
  20. extern int getpid();
  21.  
  22. @implementation TetMatrix
  23.  
  24. /*
  25.  * Some things should be set differently for color machines for either
  26.  * performances or aesthetic reasons.
  27.  */
  28. - initForColor
  29. {
  30.     const NXScreen *deepestScreen;
  31.  
  32.     deepestScreen=[NXApp colorScreen];
  33.  
  34.     if ( deepestScreen->depth  == NX_TwoBitGrayDepth ) {
  35.         anm_gameover_delta = 1.0;
  36.     } else {
  37.         anm_gameover_delta = 2.0;
  38.     }
  39.     return self;
  40. }
  41.  
  42.  
  43. - initFrame:(const NXRect *)frameRect
  44. {
  45.     NXSize size, interCell;
  46.     extern BOOL resize(NXSize *aSize);
  47.     float viewWidth, viewHeight;
  48.  
  49.     // Set the # of rows and columns in the view
  50.     [super initFrame:frameRect numRows:TETRIS_ROWS numCols:TETRIS_COLUMNS];
  51.     [self setBackgroundGray:NX_BLACK];
  52.  
  53.     size.width = size.height = 6.0;
  54.     [self setInset:&size];
  55.  
  56.     // Set the amount of space to leave b/w each block
  57.     interCell.width = interCell.height = 1.0; // 1 pixel
  58.     [self setIntercell:&interCell];
  59.  
  60.     pieceVisible = active = NO;
  61.     thePiece = [[Piece alloc] init];
  62.  
  63.     [thePiece getBlockSize:&size];
  64.  
  65.     // Should only resize in Piece.m
  66.     resize(&size);                      // Make sure block image isn't too large
  67.     [super setElementSize:&size];
  68.  
  69.     // Size Game View. 
  70.     // Default view size is (182, 438)
  71.     // Getting (176, 416)
  72.     viewWidth =  6. + TETRIS_COLUMNS * (size.width + interCell.width)  + 6.;
  73.     viewHeight = 6. + (TETRIS_ROWS)* (size.height + interCell.height) + 6.;
  74.  
  75. #ifdef DEBUG
  76.     fprintf(stderr, "Sizing the Tetris view to (%f,%f)\n", viewWidth, viewHeight);
  77. #endif
  78.     [self sizeTo: viewWidth :viewHeight];
  79.  
  80.     scoreKeeper = nil;
  81.  
  82.     anmBitmap = [[NXImage allocFromZone:[self zone]] initSize:&bounds.size];
  83.  
  84.     // Eventually want the background to be setable
  85.  
  86. //    anmBitmap = [NXImage initSize:&bounds.size];
  87. //    [anmBitmap useFromFile:"Snow.tiff"];
  88.     showNext = nil;
  89.     [self initForColor];
  90.  
  91.     gameRunning = NO;
  92.     srandom(getpid());              // Seed random # generator
  93.  
  94.     return self;
  95. }
  96.  
  97. - setupRandomFill:(int)max
  98. {
  99.     int    blockNum;
  100.     int    row, col;
  101.  
  102.     if (max > TETRIS_ROWS) max = TETRIS_ROWS;
  103.     if (max > 10) max = 10;
  104.     // fprintf (stderr, "Start random fill ...\n");
  105.     for (row = 0; row < max; row++) {
  106.         for (col = 0; col < TETRIS_COLUMNS; col++) {
  107.             if ((random () & 0x3) == 0) {   /* set 1/4 */
  108.                 blockNum = (random () & 0x3); // Rand num 0..3
  109.                 [super setBitmap: [thePiece getBlockImage:blockNum] at:row :col];
  110.             }
  111.         }
  112.     }
  113.     return self;
  114. }
  115.  
  116.  
  117. - (BOOL)acceptsFirstResponder
  118. {
  119.     return active;
  120. }
  121.  
  122. - drawSelf:(const NXRect *)rects :(int)rectCount
  123. {
  124.     [super drawSelf:rects :rectCount];
  125.     if (pieceVisible) {
  126.         NXRectClip(&insetBounds);
  127.         [thePiece draw:self];
  128.     }
  129.     return self;
  130. }
  131.  
  132.  
  133. /*
  134.  *  Act upon keyboard input from the user.
  135.  *
  136.  */
  137. - keyDown:(NXEvent *)theEvent
  138. {
  139.     unsigned short charCode;
  140.  
  141.     charCode = theEvent->data.key.charCode;
  142.     if (isupper(charCode))
  143.       charCode = tolower(charCode);
  144.  
  145.     switch (charCode) {
  146.     case 'k':
  147.     case '5':
  148.         if (charCode == 'k' || theEvent->flags & NX_NUMERICPADMASK)
  149.           [thePiece turn:self];
  150.         break;
  151.     case 'j':
  152.     case '4':
  153.         if (charCode == 'j' || theEvent->flags & NX_NUMERICPADMASK)
  154.           [thePiece left:self];
  155.         break;
  156.         
  157.     case 'l':
  158.     case '6':
  159.         if (charCode == 'l' || theEvent->flags & NX_NUMERICPADMASK)
  160.           [thePiece right:self];
  161.         break;
  162.  
  163.     case ' ':
  164.     case '2':
  165.     case '0':
  166.         if (charCode == ' ' || theEvent->flags & NX_NUMERICPADMASK) {
  167.             [self stopTimedEntry];
  168.             [thePiece drop:self];
  169.             [self startTimedEntry];
  170.         }
  171.         break;
  172.  
  173.      case 13:                          // Pause if the user hits return
  174.         [NXApp pause:self];
  175.     }
  176.     return self;
  177. }
  178.  
  179.  
  180. - setPieceVisible:(BOOL)flag
  181. {
  182.     pieceVisible = flag;
  183.     return self;
  184. }
  185.  
  186. - setScoreKeeper:keeper
  187. {
  188.     scoreKeeper = keeper;
  189.     return self;
  190. }
  191.  
  192. /*
  193.  *  Determine how fast a piece will drop.
  194.  */
  195. -(double) newDelay
  196. {
  197.     return 0.5 - 0.1 * sqrt(2.5 * level);
  198. }
  199.  
  200. - newGame:(int)theLevel
  201. {
  202.     [[super setBitmap:nil] display];
  203.     [window makeFirstResponder:self];
  204.     [self setPieceVisible:YES];
  205.     [thePiece reset:self
  206.        piece:((showNext) ? [showNext pieceInfo] : PIECE_INFO_NULL)];
  207.  
  208.     [self setupRandomFill:[randomFields intValue]];
  209.     [self display];
  210.     level = (float)theLevel;
  211.     teDelay = [self newDelay];
  212.     [self startTimedEntry];
  213.     active = YES;
  214.     return self;
  215. }
  216.  
  217. - pause:sender
  218. {
  219.     // Make TetApp the first responder
  220.     [window makeFirstResponder:window];
  221.     // Will making this object the first responder cause problems?
  222. //    [window makeFirstResponder:self];
  223.     active = NO;
  224.     return [self stopTimedEntry];
  225. }
  226.  
  227.  
  228. - continue:sender
  229. {
  230.     active = YES;
  231.     [window makeFirstResponder:self];
  232.     return [self startTimedEntry];
  233. }
  234.  
  235. - stop:sender
  236. {
  237.     active = NO;
  238.     [window makeFirstResponder:window];
  239.     return [self stopTimedEntry];
  240. }
  241.  
  242. /*
  243.  * Scroll "Game Over" down the screen after the game is finished.
  244.  */
  245. - animateGameOver
  246. {
  247.     NXSize tmpSize;
  248.     id anmGameOver;
  249.     NXRect destRect, unionRect;
  250.     
  251.     tmpSize.height = GAMEOVER_BMAP_H;
  252.     tmpSize.width = insetBounds.size.width;
  253.     anmGameOver = [[NXImage alloc] initSize:&tmpSize];
  254.  
  255.     // Composite "Game Over" to a window
  256.     [anmGameOver lockFocus];
  257.     PSsetalpha(0.0);
  258.     PSrectfill(0.0, 0.0, insetBounds.size.width, GAMEOVER_BMAP_H);
  259.     PSsetalpha(1.0);
  260.     PSgameover(insetBounds.size.width);
  261.     [anmGameOver unlockFocus];
  262.     
  263.     [super getRect:&destRect for:(int)(TETRIS_ROWS / 2) :0];
  264.     [super getRect:&unionRect for:TETRIS_ROWS - 1 :TETRIS_COLUMNS - 1];
  265.     NXUnionRect(&unionRect, &destRect);
  266.  
  267.     [anmBitmap lockFocus];
  268.     [self drawSelf:&destRect :1];
  269.     [anmBitmap unlockFocus];
  270.     
  271.     NXSetRect(&unionRect, destRect.origin.x, unionRect.origin.y,
  272.                  destRect.size.width, GAMEOVER_BMAP_H);
  273.     [self lockFocus];
  274.     NXRectClip(&destRect);
  275.  
  276.     while (unionRect.origin.y - anm_gameover_delta >= destRect.origin.y) {
  277.         unionRect.origin.y -= anm_gameover_delta;
  278.         
  279.         [anmGameOver composite:NX_SOVER toPoint:&unionRect.origin];
  280.         [[self window] flushWindow];
  281.         NXPing();
  282.         // Redraw the background to erase the "Game Over" letters 
  283. //        [anmBitmap composite:NX_COPY fromRect:&unionRect toPoint:&unionRect.origin];
  284.         [anmBitmap composite:NX_SOVER fromRect:&unionRect toPoint:&unionRect.origin];
  285.         NXPing();
  286.     }
  287.     [anmGameOver composite:NX_SOVER toPoint:&unionRect.origin];
  288.     [self unlockFocus];
  289.     
  290.     [anmGameOver free];
  291.     return self;
  292. }
  293.  
  294. /* The following methods: 
  295.  *   Animate a piece dropping.
  296.  *   Dissolve filled rows.
  297.  *   Removes filled rows from the game.
  298.  */
  299.  
  300. - (BOOL) rowFilled:(int)row
  301. {
  302.     int xc;
  303.  
  304.     for (xc = 0; xc < TETRIS_COLUMNS; xc++)
  305.         if (![super bitmapAt:row :xc])
  306.         break;
  307.     return (xc == TETRIS_COLUMNS);
  308. }
  309.  
  310. - (BOOL) rowEmpty:(int) row
  311. {
  312.     int xc;
  313.  
  314.     for (xc = 0; xc < TETRIS_COLUMNS; xc++)
  315.       if ([super bitmapAt:row :xc])
  316.         break;
  317.     return (xc == TETRIS_COLUMNS);
  318. }
  319.  
  320. - fadeFilledRows:(int) from :(int) to
  321. {
  322.     float gc;
  323.     NXRect destRect;
  324.     NXRect unionRect;
  325.     
  326.     [super getRect:&destRect for:from :0];
  327.     [super getRect:&unionRect for:to :TETRIS_COLUMNS - 1];
  328.     NXUnionRect(&unionRect, &destRect);
  329.  
  330.     // Composite the rows we are about to fade to anmBitmap
  331.     [anmBitmap lockFocus];
  332.     [self drawSelf:&destRect :1];
  333.     [anmBitmap unlockFocus];
  334.  
  335.     // Fade the blocks
  336.     [self lockFocus];
  337.     for (gc = 1.0; gc > 0.3; gc -= .03) {
  338.         PSsetgray(gc);
  339.         PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
  340.                              destRect.size.height, NX_PLUSD);
  341.         [[self window] flushWindow];
  342.         // Now redraw the blocks so we can fade them a little more
  343. //        [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&destRect.origin];
  344.         [anmBitmap composite:NX_SOVER fromRect:&destRect toPoint:&destRect.origin];
  345.         NXPing();
  346.     }
  347.     // Now make the blocks totally disappear.
  348.     PSsetgray(NX_BLACK);
  349.     PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
  350.                          destRect.size.height, NX_SOVER);
  351. //                         destRect.size.height, NX_COPY);
  352.  
  353.     [self unlockFocus];
  354.  
  355.     return self;
  356. }
  357.  
  358. /*
  359.  * Move the pieces as they are moved down on the screen after one or more rows
  360.  * has been cleared.
  361.  */
  362. - animateDrop:(int) firstFullRow :(int)firstNonFullRow :(int) lastNonEmptyRow
  363. {
  364.     NXPoint dPt;
  365.     NXRect destRect;
  366.     NXRect unionRect;
  367.  
  368.     [super point:&dPt for:firstFullRow :0]; // Place where to put falling blocks
  369.     [super getRect:&destRect for:firstNonFullRow :0];    // First row to move down
  370.     // Last row to move down
  371.     [super getRect:&unionRect for:lastNonEmptyRow :TETRIS_COLUMNS - 1];
  372.  
  373.     // We want to move everything down from the last non-full row to
  374.     // the last row with a piece in it.
  375.  
  376.     NXUnionRect(&unionRect, &destRect);
  377.     unionRect = destRect;
  378.  
  379.     // Create the image of the blocks
  380.     [anmBitmap lockFocus];
  381.     [self drawSelf:&destRect :1];
  382.     [anmBitmap unlockFocus];
  383.  
  384.     [self lockFocus];
  385.  
  386.     PSsetgray(NX_BLACK);              // The destination Rect is Black
  387.     PScompositerect(destRect.origin.x, destRect.origin.y, destRect.size.width,
  388.                          destRect.size.height, NX_COPY);
  389.  
  390.  
  391.     while (unionRect.origin.y - ANM_DELTA > dPt.y) {
  392.  
  393.         unionRect.origin.y -= ANM_DELTA;
  394.  
  395.         // First clear the window
  396.         [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&unionRect.origin];
  397.         [[self window] flushWindow];
  398.         PScompositerect(unionRect.origin.x, unionRect.origin.y, unionRect.size.width,
  399.                              unionRect.size.height, NX_COPY);
  400.         NXPing();
  401.     }
  402.     [anmBitmap composite:NX_COPY fromRect:&destRect toPoint:&dPt];
  403.     [[self window] flushWindow];
  404.  
  405.     //    NXPing();
  406.     [self unlockFocus];
  407.     return self;
  408. }
  409.  
  410. /*
  411.  *  Remove the filled rows after they have dissolved
  412.  */
  413. - removeFilledRows
  414. {
  415.     BOOL filled;
  416.     int ycfrom, ycto, yctop;
  417.     int xc, yc;
  418.     int lastFilledRow, lastNonEmptyRow, firstFilled;
  419.     int piecey = [thePiece getCurRow]; // Get the bottom-most row of the piece.
  420.  
  421.     do {
  422.         filled = NO;                  // This should only execute once?!?!
  423.  
  424.         // Work our way up from the current piece's row.
  425.         for (ycfrom = piecey; ycfrom < piecey + MAX_SHAPE_SIZE; ycfrom++) {
  426.             if ([self rowFilled: ycfrom]) {
  427.                 filled = YES;
  428.                 firstFilled = ycfrom;
  429.                 break;
  430.             }
  431.         }
  432.  
  433.         if (filled) {
  434.  
  435.             // Find the first row that isn't completely filled.
  436.             lastFilledRow = firstFilled;    // Loop can execute 0 times  
  437.             for (ycto = firstFilled + 1; ycto < firstFilled + MAX_SHAPE_SIZE; ycto++) {
  438.                 if ([self rowFilled: ycto]) {
  439.                     lastFilledRow = ycto;
  440.                 } else {
  441.                     break;
  442.                 }
  443.             }
  444.             // Find the topmost row in the program.  A row with at least one block.
  445.             // Assert: Must enter loop at least once.
  446.             for (yctop = lastFilledRow+1; yctop < TETRIS_ROWS; yctop++) {
  447.                 if (! [self rowEmpty: yctop]) {
  448.                     lastNonEmptyRow = yctop; // But do I have to execute this once?
  449.                 } else {
  450.                     break;
  451.                 }
  452.             }
  453.             [self fadeFilledRows: firstFilled :lastFilledRow];
  454.  
  455.             // Animate the all of the pieces as the move down.
  456.             [self animateDrop:firstFilled :lastFilledRow+1 :lastNonEmptyRow];
  457.  
  458.  
  459.             // Move the rows above the faded rows down to take their place.
  460.  
  461.             for (yc = lastFilledRow+1; yc <=lastNonEmptyRow; yc++) {
  462.                 for (xc = 0; xc < TETRIS_COLUMNS; xc++) // For each block in the row.
  463.                   [super setBitmap:[self bitmapAt:yc :xc] at:firstFilled :xc];
  464.                 firstFilled++;
  465.             }
  466.  
  467.             // Clear the rows at the very top that have been moved down
  468.             // and which have nothing to take their place.
  469.  
  470.             for (yc = firstFilled; yc <=lastNonEmptyRow; yc++) {
  471. #ifdef DEBUG
  472.                 printf("Removing row %d\n", yc); 
  473. #endif
  474.                 for (xc = 0; xc < TETRIS_COLUMNS; xc++)
  475.                   [super setBitmap:nil at:yc :xc];
  476.             }
  477.             piecey++;
  478.         }
  479.     } while (filled);
  480.     return self;
  481. }
  482.  
  483.  
  484. /* The main animation routines follow.  A timed entry routine is setup
  485.  * so that it calls teHandler at a regular interval (depending on the
  486.  * level)
  487.  */
  488.  
  489. /*
  490.  *  The timed entry handler that gets called every teDelay seconds.
  491.  *
  492.  */
  493. - step
  494. {
  495.     static int downWait = 0;
  496.  
  497.     // Move the piece down.  If it can't move down make it stick,
  498.     // remove filled rows, and generate a new piece.
  499.  
  500.     if (![thePiece down:self]) {
  501.  
  502.         // After the piece sticks to the bottom, wait for a while before
  503.         // we send the next piece.  The wait depends on the current level.
  504.  
  505.         if (++downWait >= level) {    // Don't commit the piece immediately
  506.             downWait = 0;
  507.             [self stopTimedEntry];
  508.             [self setPieceVisible:NO];
  509.             [thePiece stick:self]; // Make the piece stick
  510.             [self removeFilledRows];
  511.             [self setPieceVisible:YES];
  512.             if (scoreKeeper) {
  513.                 [scoreKeeper addScore:[thePiece points]];
  514.                 level += (MAX_LEVEL - level) / 400.0;
  515.                 teDelay = [self newDelay];
  516.             }
  517.             // show the next piece.
  518.             if ([thePiece reset:self piece:((showNext) ?
  519.                                       [showNext pieceInfo] : PIECE_INFO_NULL)])
  520.               [self startTimedEntry];
  521.             else {
  522.                 [self animateGameOver];
  523.                 [[self window] makeFirstResponder:[self window]];
  524.                 active = NO;
  525.                 [NXApp gameOver];
  526.             }
  527.         }
  528.  
  529.     } else {                              // The piece can't go down
  530.         downWait = 0;                  // Set a delay before showing next piece
  531.     }
  532.     return self;
  533. }
  534.  
  535. static void runOneStep (DPSTimedEntry timedEntry, double timeNow, void *data)
  536. {
  537.     [(id)data step];
  538. }
  539.  
  540. - startTimedEntry
  541. {
  542.     if (!gameRunning) {
  543.         timedEntryNum = DPSAddTimedEntry(teDelay,&runOneStep,
  544.                                                     self, NX_BASETHRESHOLD);
  545.         gameRunning = YES;
  546.     }
  547.     return self;
  548. }
  549.  
  550. - stopTimedEntry
  551. {
  552.     if (gameRunning) {
  553.         DPSRemoveTimedEntry(timedEntryNum);
  554.         gameRunning = NO;
  555.     }
  556.     return self;
  557. }
  558.  
  559. /*
  560.  * Clean up.
  561.  */
  562. - free
  563. {
  564.     [thePiece free];
  565.     [anmBitmap free];
  566.     return [super free];
  567. }
  568.  
  569. @end
  570.